﻿using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace SpiderSync
{
    /// <summary>
    /// Static class for manipulationg filesystem
    /// </summary>
    static class FilesystemUtils
    {
        public static List<String> excludePatterns { get; set; }

        /// <summary>
        /// Check if path exists
        /// </summary>
        /// <param name="path">Path to check</param>
        /// <returns>True if path exists, otherwise false.</returns>
        public static bool PathExists(string path)
        {
            return File.Exists(path) || Directory.Exists(path);
        }

        /// <summary>
        /// Check if path is directory
        /// </summary>
        /// <param name="path">Path to check</param>
        /// <returns>True if path is directory, false otherwise.</returns>
        public static bool IsDir(string path)
        {
            return File.GetAttributes(path).HasFlag(FileAttributes.Directory);
        }

        /// <summary>
        /// Copies a file or a directory (not recursive!)
        /// </summary>
        /// <param name="source">Source path</param>
        /// <param name="destination">Destination path</param>
        public static void Copy(string source, string destination)
        {
            if (PathExists(source))
            {
                if (IsDir(source))
                {
                    Directory.CreateDirectory(destination);
                }
                else
                {
                    File.Copy(source, destination, true);
                }
            }
        }

        /// <summary>
        /// Deletes a file or directory if exists
        /// </summary>
        /// <param name="path">PAth to delete</param>
        public static void Delete(string path)
        {
            if (PathExists(path))
            {
                if (IsDir(path))
                {
                    Directory.Delete(path, true);
                }
                else
                {
                    File.Delete(path);
                }
            }
        }

        /// <summary>
        /// Moves a file or directory if exists
        /// </summary>
        /// <param name="source">Source path</param>
        /// <param name="destination">Destination path</param>
        public static void Rename(string source, string destination)
        {
            if (PathExists(source))
            {
                if (IsDir(source))
                {
                    Directory.Move(source, destination);
                }
                else
                {
                    File.Move(source, destination);
                }
            }
        }

        /// <summary>
        /// Get first existing parent path.
        /// </summary>
        /// <param name="path">Path to check</param>
        /// <returns>First existing parent path if found, otherwise null.</returns>
        public static string GetClosestExistingParentPath(string path)
        {
            string parentPath = path.TrimEnd(Path.DirectorySeparatorChar);
            do
            {
                parentPath = Path.GetDirectoryName(parentPath);
            } while (parentPath != null && parentPath.Length > 0 && !PathExists(parentPath));

            return parentPath != null && parentPath.Length > 0 ? parentPath : null;
        }
      
        /// <summary>
        /// Check is an IOException occured by a locked file
        /// </summary>
        /// <param name="e">IOException to check</param>
        /// <returns>True if exception occured by a file lock, false otherwise</returns>
        public static bool IsFileLockedException(IOException e)
        {
            int errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
            return errorCode == 32 || errorCode == 33;
        }

      
        /// <summary>
        /// Get relative path from full path and root
        /// </summary>
        /// <param name="path">Full path</param>
        /// <param name="root">Root path</param>
        /// <returns>Relative path if path is under root, otherwise null</returns>
        public static string GetRelativePath(string path, string root)
        {   
            if (path.StartsWith(root))
            {
                return path.Substring(root.Length + 1);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Synchronize source directory to destination directory with robocopy.
        /// </summary>
        /// <param name="source">Source directory</param>
        /// <param name="destination">Destination directory</param>
        /// <param name="delete">Delete missing files from destination</param>
        /// <param name="retries">Max retry count</param>
        public static void SyncDirectories(string source, string destination, bool delete, int retries)
        {
            if (!PathExists(source))
            {
                throw new DirectoryNotFoundException("Source directory not found: " + source);
            }   

            Process Robocopy = new Process();

            Robocopy.StartInfo.FileName = "robocopy";
            string argumets = source + " " + destination + (delete ? " /MIR" : " /E") + " /W:1 /R:" + retries + " /NFL /NDL /NJH /NJS /NS /NC /NP";

            if (excludePatterns.Count > 0)
            {
                argumets += " /XF " + String.Join(" /XF ", excludePatterns.ToArray());
            }

            Robocopy.StartInfo.Arguments = argumets;
            Robocopy.StartInfo.UseShellExecute = false;

            Robocopy.Start();            
            Robocopy.WaitForExit();
            Robocopy.Close();
        }

        /// <summary>
        /// Check if given path mathes to any wildcard in list
        /// </summary>
        /// <param name="wildcard">Wildcards to match</param>
        /// <param name="path">Path to check</param>
        /// <returns>True if matches, false if not</returns>
        public static bool IsMatchWildcards(List<String> wildcard, String path)
        {
            return WildcardsToRegex(wildcard).Match(path).Success;
        }

        /// <summary>
        /// Converts multiple wildcard pattern to Regex
        /// </summary>
        /// <param name="wildcard">Wildcard string list to compare</param>
        /// <returns>Converted Regex</returns>
        private static Regex WildcardsToRegex(List<String> wildcards)
        {
            return new Regex("(" + String.Join("|", wildcards).Replace("?", ".").Replace("*", "[^/]*") + ")");
        }
    }
}
